package cn.lg.soar.mvc.aspect.idempotent;

import cn.lg.soar.common.algorithm.CryptoUtils;
import cn.lg.soar.common.algorithm.serial.Snowflake;
import cn.lg.soar.common.data.msg.I18n;
import cn.lg.soar.common.exceptions.IdempotentException;
import cn.lg.soar.common.util.IpUtil;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.reflect.ReflectUtil;
import cn.lg.soar.core.annotations.IdempotentByKey;
import cn.lg.soar.core.annotations.IdempotentByParam;
import cn.lg.soar.core.annotations.IdempotentByToken;
import cn.lg.soar.core.aspect.AopEvaluationContext;
import cn.lg.soar.core.aspect.SpelExpressionUtils;
import cn.lg.soar.core.config.cache.template.ICacheTemplate;
import cn.lg.soar.mvc.util.ServletUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author luguoxiang
 * @date 2021/8/24
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Aspect
@Order(0)
@ConditionalOnProperty(value = "lg.idempotent.enabled", havingValue = "true", matchIfMissing = true)
public class IdempotentAspect {

    @Autowired
    private IdempotentService idempotentService;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private ICacheTemplate<String, Object> cacheTemplate;

    private static final String BY_KEY = "md:key:";
    private static final String BY_PARAM = "md:param:";

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, IdempotentByKey idempotent) throws Throwable{
        HttpServletRequest request = ServletUtils.getRequest();
        String key = request.getHeader("idempotent-token");
        if (key == null) {
            key = request.getParameter("idempotent-token");
        }
        if (key == null) {
            throw new IdempotentException(201, I18n.build("缺少幂等性key"));
        }
        key = BY_KEY + key;
        // 校验幂等性
        Long value = Snowflake.nextValue();
        if (cacheTemplate.putIfAbsent(key, value, idempotent.expire())) {
            try {
                return point.proceed();
            } catch (Exception e) {
                // 若失败则清除黑名单
                if (Objects.equals(cacheTemplate.get(key), value)) {
                    cacheTemplate.delete(key);
                }
                throw e;
            }
        }
        throw new IdempotentException(I18n.build(idempotent.message()));
    }

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, IdempotentByToken idempotent) throws Throwable{
        HttpServletRequest request = ServletUtils.getRequest();
        String token = request.getHeader("idempotent-token");
        if (token == null) {
            token = request.getParameter("idempotent-token");
        }
        if (token == null) {
            throw new IdempotentException(202, I18n.build("请刷新页面后重新提交"));
        }
        // 校验幂等性
        if (this.idempotentService.checkTokenByIp(IpUtil.getRemoteIpInt(request), token)) {
            return point.proceed();
        }
        throw new IdempotentException(I18n.build(idempotent.message()));
    }

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, IdempotentByParam idempotent) throws Throwable{
        AopEvaluationContext context = new AopEvaluationContext(point, this.applicationContext, null);

        String[] values = idempotent.value().split(idempotent.separator());
        StringBuilder sb = new StringBuilder();
        // 保留最后一个
        int len = values.length - 1;
        for (int i = 0; i < len; i++) {
            Object v = SpelExpressionUtils.getValue(values[i], context);
            sb.append(i).append('=').append(v).append(';');
        }

        // 读取最后一个
        Object obj = SpelExpressionUtils.getValue(values[len], context);
        if (obj instanceof String) {
            sb.append(len).append('=').append(obj).append(';');
        } else {
            Map<String, Object> map = new HashMap<>(32);
            if (idempotent.properties().length == 0) {
                // 不过滤名单
                ReflectUtil.scanField(obj.getClass(), x -> {
                    map.put(x.getName(), ReflectUtil.get(obj, x));
                });
            } else {
                // 过滤名单
                Set<String> properties = Arrays.stream(idempotent.properties())
                        .collect(Collectors.toSet());
                if (IdempotentByParam.Mode.EXCLUDE.equals(idempotent.mode())) {
                    // 黑名单
                    ReflectUtil.scanField(obj.getClass(), x -> {
                        String name = x.getName();
                        if (properties.contains(name)) {
                            // 排除属性
                            return;
                        }
                        map.put(name, ReflectUtil.get(obj, x));
                    });
                } else {
                    // 白名单
                    ReflectUtil.scanField(obj.getClass(), x -> {
                        String name = x.getName();
                        if (properties.contains(name)) {
                            // 包含属性
                            map.put(name, ReflectUtil.get(obj, x));
                        }
                    });
                }
            }
            // 拼接
            map.entrySet().stream()
                    .sorted((a, b) -> a.getKey().compareTo(b.getKey()))
                    .forEach(x -> sb.append(x.getKey()).append('=').append(x.getValue()).append(';'));
        }

        // 取信息摘要 生成key
        byte[] bytes = CryptoUtils.md5(sb.toString().getBytes());
        String key = BY_PARAM + DataUtil.byteToBase64(bytes);

        // 校验幂等性
        Long value = Snowflake.nextValue();
        if (cacheTemplate.putIfAbsent(key, value, idempotent.expire())) {
            try {
                return point.proceed();
            } catch (Exception e) {
                // 若失败则清除黑名单
                if (Objects.equals(cacheTemplate.get(key), value)) {
                    cacheTemplate.delete(key);
                }
                throw e;
            }
        }
        throw new IdempotentException(I18n.build(idempotent.message()));
    }

}
